// ...........................................................................................................
// (C) Copyright  1996/2008 Fuego Inc.  All Rights Reserved
// THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF Fuego Inc.
// The copyright notice above does not evidence any actual or intended
// publication of such source code.
//
// Last changed on 2008-01-11 18:11:44 (-0300), by: machaval. $Revision$
// ...........................................................................................................

package com.webcamtracker.video;

import com.webcamtracker.configuration.RootPref;
import com.webcamtracker.image.ImageProvider;
import com.webcamtracker.video.utils.JMFUtils;

import javax.media.Buffer;
import javax.media.CaptureDeviceInfo;
import javax.media.Codec;
import javax.media.ConfigureCompleteEvent;
import javax.media.Control;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.EndOfMediaEvent;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.NoProcessorException;
import javax.media.PrefetchCompleteEvent;
import javax.media.Processor;
import javax.media.RealizeCompleteEvent;
import javax.media.ResourceUnavailableEvent;
import javax.media.Time;
import javax.media.UnsupportedPlugInException;
import javax.media.control.FormatControl;
import javax.media.control.FrameGrabbingControl;
import javax.media.control.FrameRateControl;
import javax.media.control.TrackControl;
import javax.media.format.VideoFormat;
import javax.media.util.BufferToImage;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class VideoManager
        implements ImageProvider {
    //~ Instance fields ......................................................................................

    private Codec[] codecs;
    private FrameGrabbingControl frameGrabber;
    private boolean isStarted = false;
    private Processor processor;
    private final VideoManager.StateListener stateListener = new StateListener();

    //~ Constructors .........................................................................................

    public VideoManager() {
    }

    public VideoManager(Codec[] codecs) {
        this.codecs = codecs;
    }


    public Vector getWebCamDeviceInfo() {
        String encoding = RootPref.getWebCamPref().get(RootPref.KEY_WEBCAM_ENCODING, RootPref.DEF_VALUE_WEBCAM_ENCODING);
        VideoFormat vf = new VideoFormat(encoding);
        return JMFUtils.findCaptureDevice(vf);
    }

    public void startWebCam(CaptureDeviceInfo captureDeviceInfo, VideoFormat format) {
        if (!isStarted()) {
            try {

                final MediaLocator ml = captureDeviceInfo.getLocator();
                if (ml == null) {
                    log.log(Level.WARNING, "! No webcam Devices for ");
                    return;
                }

                startMedia(ml, format);
            }
            catch (IOException e) {
                log.log(Level.SEVERE, "Error trying to find a camera", e);
            }
            catch (NoProcessorException e) {
                log.log(Level.SEVERE, "Error trying to find a camera", e);
            }
        }
    }

    public void startVideo(String url) {
        try {
            startMedia(new MediaLocator(url));
        }
        catch (IOException e) {
            log.log(Level.SEVERE, "Error trying to run video " + url, e);
        }
        catch (NoProcessorException e) {
            log.log(Level.SEVERE, "Error trying to run video " + url, e);
        }
    }

    public Component getVideoComponent() {
        return !isStarted() ? null : processor.getVisualComponent();
    }

    public Processor getProcessor() {
        return processor;
    }

    /**
     * For polling service
     *
     * @return a BufferedImage
     */
    public BufferedImage getImage() {
        if (isStarted()) {
            if (frameGrabber == null) {
                frameGrabber =
                        (FrameGrabbingControl) processor.getControl("javax.media.control.FrameGrabbingControl");
            }

            Buffer buf = frameGrabber.grabFrame();

            // Convert frame to an buffered image and return
            Image img = (new BufferToImage((VideoFormat) buf.getFormat()).createImage(buf));
            BufferedImage buffImg =
                    new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
            Graphics2D g = buffImg.createGraphics();
            g.drawImage(img, null, null);
            return buffImg;
        }

        return null;
    }

    public void stopVideo() {
        if (isStarted()) {
            processor.close();
        }

        isStarted = false;
    }

    private void startMedia(MediaLocator ml) throws IOException, NoProcessorException {
        startMedia(ml, null);
    }

    private void startMedia(MediaLocator ml, VideoFormat format)
            throws IOException, NoProcessorException {
        log.log(Level.INFO, "Device found at " + ml.toString());
        processor = Manager.createProcessor(ml);
        processor.addControllerListener(stateListener);

        log.log(Level.INFO, "Start Configuring");
        processor.configure();

        if (!waitForState(Processor.Configured)) {
            log.log(Level.WARNING, "Failed to configure the processor.");
            return;
        }

        // So I can use it as a player.
        processor.setContentDescriptor(null);

        Control control = getFrameRateControl();

        if (control != null && control instanceof FrameRateControl) {
            FrameRateControl frameRateControl = (FrameRateControl) control;
            frameRateControl.setFrameRate(frameRateControl.getMaxSupportedFrameRate());
        }
        if (format != null) {
            getFormatControl().setFormat(format);
        }

        // Obtain the track controls.
        TrackControl[] tc = processor.getTrackControls();

        if (tc == null) {
            log.log(Level.WARNING, "Failed to obtain track controls from the processor.");
            return;
        }

        log.log(Level.INFO, "Search Track Control");

        // Search for the track control for the video track.
        TrackControl videoTrack = null;

        for (TrackControl aTc : tc) {
            if (aTc.getFormat() instanceof VideoFormat) {
                videoTrack = aTc;
                break;
            }
        }

        if (videoTrack == null) {
            log.log(Level.WARNING, "The input media does not contain a video track.");
            return;
        }

        log.log(Level.INFO, "Video format: " + videoTrack.getFormat());

        try {
            if (codecs != null) {
                videoTrack.setCodecChain(codecs);
            }
        }
        catch (UnsupportedPlugInException e) {
            log.log(Level.WARNING, "The process does not support effects.");
        }

        processor.prefetch();

        if (!waitForState(Processor.Prefetched)) {
            log.log(Level.WARNING, "Failed to realize the processor.");
        }

        processor.start();

        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (!waitForState(Processor.Started)) {
            log.log(Level.WARNING, "Failed to start the processor.");
        }

        log.log(Level.INFO, "Media " + ml + " Configured correcltly");
        isStarted = true;
    }

    private Control getFrameRateControl() {
        return processor.getControl("javax.media.control.FrameRateControl");
    }

    public boolean isStarted() {
        return isStarted;
    }

    public VideoFormat getVideoFormat() {
        final FormatControl control = getFormatControl();
        return (VideoFormat) control.getFormat();
    }

    private FormatControl getFormatControl() {
        return (FormatControl) processor.getControl("javax.media.control.FormatControl");
    }

    private boolean waitForState(int state) {
        while (processor.getState() != state && processor.getState() != Processor.Unrealized) {
            try {
                synchronized (stateListener) {
                    stateListener.wait();
                }
            }
            catch (InterruptedException e) {
                //ignore
            }
        }

        return processor.getState() != Processor.Unrealized;
    }

    //~ Static fields/initializers ...........................................................................

    public static Logger log = Logger.getLogger("VideoManager");

    //~ Inner Classes ........................................................................................

    private class StateListener
            implements ControllerListener {
        public void controllerUpdate(ControllerEvent evt) {
            if (evt instanceof ConfigureCompleteEvent || evt instanceof RealizeCompleteEvent ||
                    evt instanceof PrefetchCompleteEvent) {
                synchronized (this) {
                    this.notifyAll();
                }
            } else if (evt instanceof ResourceUnavailableEvent) {
                synchronized (this) {
                    log.log(Level.WARNING, "Resource Unavailable");
                    this.notifyAll();
                }
            } else if (evt instanceof EndOfMediaEvent) {
                processor.setMediaTime(new Time(0));
                processor.start();
            }
        }
    }
}
